package swa08.provider;

import java.util.Vector;
import java.util.Hashtable;
import java.util.Collection;
import java.util.Properties;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;

import swa2012.telco.broker.beans.Client;
import swa2012.telco.broker.beans.Provider;
import swa2012.telco.broker.beans.Message;
import swa2012.telco.broker.beans.BrokerSendMessageStatus;
import swa2012.telco.broker.beans.ReceiveMessageStatus;
import swa2012.telco.broker.BrokerCallback;
import swa2012.telco.broker.Broker;

import org.apache.cxf.Bus;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.jaxws.JaxWsServerFactoryBean;

public class ServiceImpl implements Service
{
	private Properties conf;
	private Provider provider;
	private Broker broker;
	//private Vector<Message> messages = null;
	private Hashtable<String, Vector<Message>> messages = null;
	private Vector<Client> clients = null;

	/**
	 * Initialization.
	 */
	public ServiceImpl(String baseDir, Bus bus, Properties defaultConf)
	{
		// Init lists
		this.messages = new Hashtable<String, Vector<Message>>();
		this.clients = new Vector<Client>();

		// Init configuration
		conf = new Properties(defaultConf);

		try {
			File file = new File(baseDir + "/config.properties");
			if(file.exists())
				conf.load(new FileInputStream(file));

			// Publish Provider
			JaxWsServerFactoryBean svrFactory = new JaxWsServerFactoryBean();
			svrFactory.setBus(bus);
			svrFactory.setServiceClass(Service.class);
			svrFactory.setAddress(conf.getProperty("provider.address"));
			svrFactory.setServiceBean(this);
			svrFactory.create();

			// Init Broker
			JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
			factory.setAddress(conf.getProperty("broker.address"));
			broker = factory.create(Broker.class);

			// Create Provider bean
			provider = new Provider(
				conf.getProperty("provider.id"),
				new URI( // Build the whole endpoint address
					conf.getProperty("provider.baseUrl")
					+ conf.getProperty("provider.contextPath")
					+ conf.getProperty("provider.address")
				)
			);
		}
		catch(Exception e) {
			throw new RuntimeException("Initialization failed.", e);
		}

		// Log some data for debugging
		log("provider.id: " + conf.getProperty("provider.id"));
		log("provider.baseUrl: " + conf.getProperty("provider.baseUrl"));
		log("provider.contextPath: " + conf.getProperty("provider.contextPath"));
		log("provider.address: " + conf.getProperty("provider.address"));
		log("provider: " + provider);

		// Register the Provider with the Broker
		// Use new thread to avoid deadlock
		new Thread(new Runnable() {
			public void run() {
				log("Registeration: " + broker.registerProvider(provider));
			}
		}).start();
	}

	public Client connectClient(String id)
	{
		System.out.println("Connecting client with id " + id);
		for (int i = 0; i < this.clients.size(); i++)
		{
			if (this.clients.elementAt(i).getID().compareTo(id) == 0)
			{
				// client already existed, not connecting the new one
				return this.clients.elementAt(i);
			}
		}
		
		// no client existed, add it to the list
		try
		{
			Client client = new Client(id, this.provider);
			this.clients.add(client);
			return client;
		}
		catch (Exception e)
		{
			e.printStackTrace();
			return null;
		}
	}

	public boolean disconnectClient(Client client)
	{
		System.out.println("Disconnecting client with id " + client.getID());
		for (int i = 0; i < this.clients.size(); i++)
		{
			if (this.clients.elementAt(i).getID().compareTo(client.getID()) == 0)
			{
				try
				{
					this.clients.remove(i);
				}
				catch (Exception e)
				{
					e.printStackTrace();
					return false;
				}
				return true;
			}
		}
		
		return false;
	}

	public BrokerSendMessageStatus sendMessage(Message message)
	{
		BrokerSendMessageStatus status = null;
		if (message.getReceiver().getProvider().getID().compareTo(this.provider.getID()) == 0)
		{
			ReceiveMessageStatus stat = receiveMessage(message);
			if (stat.equals(ReceiveMessageStatus.RECEIVE_MESSAGE_OK))
			{
				status = BrokerSendMessageStatus.SEND_MESSAGE_STORED;
			}
		}
		else
		{
			status = broker.sendMessage(message);
		}

		System.out.println("ServiceImpl-sendMessage ('" + message.getText() + "', '" + message.getSender() + "', '" + message.getReceiver() + "'): " + status);
		return status;
	}

	public ReceiveMessageStatus receiveMessage(Message msg)
	{
		System.out.println("ServiceImpl-receiveMessage ('" + msg.getText() + "', '" + msg.getSender() + "', '" + msg.getReceiver() + "')");
		// check if the receiver already has some messages stored
		if (this.messages.containsKey(msg.getReceiver().getID()))
		{
			// if the message already existed, do not accept the new one
			if (this.messages.get(msg.getReceiver().getID()).contains(msg))
			{
				return ReceiveMessageStatus.RECEIVE_MESSAGE_ERROR;
			}
			else
			{
				// store the new message
				try
				{
					this.messages.get(msg.getReceiver().getID()).add(msg);
				}
				catch (Exception e)
				{
					e.printStackTrace();
					return ReceiveMessageStatus.RECEIVE_MESSAGE_ERROR;
				}
				return ReceiveMessageStatus.RECEIVE_MESSAGE_OK;
			}
		}
		// receiver has no stored messages yet. Create a new entry in the hashtable
		else
		{
			try
			{
				Vector<Message> tmp = new Vector<Message>();
				tmp.add(msg);
				this.messages.put(msg.getReceiver().getID(), tmp);
			}
			catch (Exception e)
			{
				e.printStackTrace();
				return ReceiveMessageStatus.RECEIVE_MESSAGE_ERROR;
			}
			return ReceiveMessageStatus.RECEIVE_MESSAGE_OK;
		}
	}

	public Collection<Message> pullMessages(Client client)
	{
		// check if messages are available for the specified client and return them
		if (this.messages.containsKey(client.getID()))
		{
			return this.messages.get(client.getID());
		}
		// no messages available. return null
		else
		{
			return null;
		}
	}

	public Vector<Message> pullMessage(Client client)
	{
		// check if messages are available for the specified client and return them
		if (this.messages.containsKey(client.getID()))
		{
			return this.messages.get(client.getID());
		}
		// no messages available. return null
		else
		{
			return null;
		}
	}
	
	@Override
	public Collection<Client> listClients()
	{
		return this.clients;
	}

	public Vector<Client> listClient()
	{
		return this.clients;
	}
	
	public boolean ping()
	{
		return true;
	}

	private void log(String msg)
	{
		System.out.println(msg);
	}

	/**
	 * Destruction sequence.
	 * Not to be exposed via service, hence the package visibility.
	 */
	void destroy()
	{
		log("Deregisteration: " + broker.deregisterProvider(provider));
	}

	/**
	 * The finelizer.
	 */
	protected void finalize() throws Throwable
	{
		destroy();
		super.finalize();
	}
}
